// Copyright (c) 2007 Robin Boerdijk. All rights reserved.
//
// This software is licensed under the terms of the Microsoft Reciprocal License as described 
// in the file LICENSE.txt, which you should have received as part of this distribution. The 
// license terms are also available at http://www.opensource.org/licenses/ms-rl.html.

#include "wtkpc.h"

#include "wtk/GridBox.h"
#include "wtk/FillerBox.h"

// get rid of min/max macros defined by windows.h

#ifdef min
#undef min
#endif

#ifdef max
#undef max
#endif

#include <limits>

namespace wtk
{
	GridBox::GridBox(Count columnCount, Count expectedRowCount, int flags)
		: ContainerBox(columnCount * expectedRowCount)
		, m_columnCount(columnCount)
		, m_flags(flags)
		, m_cachedLargestMinimumColumnWidth(-1)
		, m_cachedLargestMinimumRowHeight(-1)
		, m_cachedSmallestColumnElasticity(-1)
		, m_cachedSmallestRowElasticity(-1)
	{
		m_cachedMinimumColumnWidths.reserve(columnCount);
		m_cachedMinimumRowHeights.reserve(expectedRowCount);
		m_cachedColumnElasticities.reserve(columnCount);
		m_cachedRowElasticities.reserve(expectedRowCount);
	}

	GridBox::Count GridBox::GetRowCount()
	{
		Count fullRowCount = m_boxes.size() / m_columnCount;
		if ((m_boxes.size() % m_columnCount) == 0) {
			return fullRowCount;
		} else {
			return fullRowCount + 1;
		}
	}

	Box *GridBox::GetBox(Count rowNumber, Count columnNumber)
	{
		Count i = (rowNumber * m_columnCount) + columnNumber;
		if (i < m_boxes.size()) {
			return m_boxes[i];
		} else {
			return NULL;
		}
	}

	long GridBox::GetCached(std::vector<long> &cache, Count index, long (GridBox::*calculateMethod)(Count))
	{
		if (index >= cache.size()) {
			cache.resize(index + 1, -1);
		}
		long value = cache[index];
		if (value < 0) {
			value = (this->*calculateMethod)(index);
			cache[index] = value;
		}
		return value;
	}

	long GridBox::CalculateLargest(Count count, long (GridBox::*getMethod)(Count))
	{
		long largestValue = 0;
		for (Count i = 0; i < count; i++) {
			long value = (this->*getMethod)(i);
			if (value > largestValue) {
				largestValue = value;
			}
		}
		return largestValue;
	}

	long GridBox::CalculateSmallest(Count count, long (GridBox::*getMethod)(Count))
	{
		long smallestValue = 0;
		for (Count i = 0; i < count; i++) {
			long value = (this->*getMethod)(i);
			if (value == 0) {
				return 0;
			} else if ((i == 0) || (value < smallestValue)) {
				smallestValue = value;
			}
		}
		return smallestValue;
	}

	long GridBox::CalculateMinimumColumnWidth(Count columnNumber)
	{
		long largestWidth = 0;
		for (Count rowNumber = 0, rowCount = GetRowCount(); rowNumber < rowCount; rowNumber++) {
			Box *box = GetBox(rowNumber, columnNumber);
			if (box != NULL) {
				long width = box->GetMinimumSize().cx;
				if (width > largestWidth) {
					largestWidth = width;
				}
			}
		}
		return largestWidth;
	}

	long GridBox::GetMinimumColumnWidth(Count columnNumber)
	{
		return GetCached(m_cachedMinimumColumnWidths, columnNumber, &GridBox::CalculateMinimumColumnWidth);
	}

	long GridBox::GetLargestMinimumColumnWidth()
	{
		if (m_cachedLargestMinimumColumnWidth < 0) {
			m_cachedLargestMinimumColumnWidth = CalculateLargest(m_columnCount, &GridBox::GetMinimumColumnWidth);
		}
		return m_cachedLargestMinimumColumnWidth;
	}

	long GridBox::CalculateMinimumRowHeight(Count rowNumber)
	{
		long largestHeight = 0;
		for (Count columnNumber = 0, columnCount = m_columnCount; columnNumber < columnCount; columnNumber++) {
			Box *box = GetBox(rowNumber, columnNumber);
			if (box != NULL) {
				long height = box->GetMinimumSize().cy;
				if (height > largestHeight) {
					largestHeight = height;
				}
			}
		}
		return largestHeight;
	}

	long GridBox::GetMinimumRowHeight(Count rowNumber)
	{
		return GetCached(m_cachedMinimumRowHeights, rowNumber, &GridBox::CalculateMinimumRowHeight);
	}

	long GridBox::GetLargestMinimumRowHeight()
	{
		if (m_cachedLargestMinimumRowHeight < 0) {
			m_cachedLargestMinimumRowHeight = CalculateLargest(GetRowCount(), &GridBox::GetMinimumRowHeight);
		}
		return m_cachedLargestMinimumRowHeight;
	}

	SIZE GridBox::CalculateMinimumContentSize()
	{
		SIZE minimumContentSize(Zero<SIZE>());

		if (m_flags & EqualizeColumnWidths) {
			minimumContentSize.cx = m_columnCount * GetLargestMinimumColumnWidth();
		} else {
			for (Count columnNumber = 0, columnCount = m_columnCount; columnNumber < columnCount; columnNumber++) {
				minimumContentSize.cx += GetMinimumColumnWidth(columnNumber);
			}
		}

		if (m_flags & EqualizeRowHeights) {
			minimumContentSize.cy = GetRowCount() * GetLargestMinimumRowHeight();
		} else {
			for (Count rowNumber = 0, rowCount = GetRowCount(); rowNumber < rowCount; rowNumber++) {
				minimumContentSize.cy += GetMinimumRowHeight(rowNumber);
			}
		}

		return minimumContentSize;
	}

	long GridBox::CalculateColumnElasticity(Count columnNumber)
	{
		long minimumElasticity = 0;
		for (Count rowNumber = 0, rowCount = GetRowCount(); rowNumber < rowCount; rowNumber++) {
			Box *box = GetBox(rowNumber, columnNumber);
			if (box == NULL) {
				return 0; // missing box has no elasticity
			} else {
				long elasticity = box->GetHorizontalElasticity();
				if (elasticity == 0) {
					return 0; // no need to continue;
				} else if ((rowNumber == 0) || (elasticity < minimumElasticity)) {
					minimumElasticity = elasticity;
				}
			}
		}
		return minimumElasticity;
	}

	long GridBox::GetColumnElasticity(Count columnNumber)
	{
		return GetCached(m_cachedColumnElasticities, columnNumber, &GridBox::CalculateColumnElasticity);
	}

	long GridBox::GetSmallestColumnElasticity()
	{
		if (m_cachedSmallestColumnElasticity < 0) {
			m_cachedSmallestColumnElasticity = CalculateSmallest(m_columnCount, &GridBox::GetColumnElasticity);
		}
		return m_cachedSmallestColumnElasticity;
	}

	long GridBox::CalculateHorizontalElasticity()
	{
		if (m_flags & EqualizeColumnWidths) {
			return m_columnCount * GetSmallestColumnElasticity();
		} else {
			long totalElasticity = 0;
			for (Count columnNumber = 0, columnCount = m_columnCount; columnNumber < columnCount; columnNumber++) {
				totalElasticity += GetColumnElasticity(columnNumber);
			}
			return totalElasticity;
		}
	}

	long GridBox::CalculateRowElasticity(Count rowNumber)
	{
		long minimumElasticity = 0;
		for (Count columnNumber = 0, columnCount = m_columnCount; columnNumber < columnCount; columnNumber++) {
			Box *box = GetBox(rowNumber, columnNumber);
			if (box == NULL) {
				return 0; // missing box has no elasticity
			} else {
				long elasticity = box->GetVerticalElasticity();
				if (elasticity == 0) {
					return 0; // no need to continue;
				} else if ((columnNumber == 0) || (elasticity < minimumElasticity)) {
					minimumElasticity = elasticity;
				}
			}
		}
		return minimumElasticity;
	}

	long GridBox::GetRowElasticity(Count rowNumber)
	{
		return GetCached(m_cachedRowElasticities, rowNumber, &GridBox::CalculateRowElasticity);
	}

	long GridBox::GetSmallestRowElasticity()
	{
		if (m_cachedSmallestRowElasticity < 0) {
			m_cachedSmallestRowElasticity = CalculateSmallest(GetRowCount(), &GridBox::GetRowElasticity);
		}
		return m_cachedSmallestRowElasticity;
	}

	long GridBox::CalculateVerticalElasticity()
	{
		if (m_flags & EqualizeRowHeights) {
			return GetRowCount() * GetSmallestRowElasticity();
		} else {
			long totalElasticity = 0;
			for (Count rowNumber = 0, rowCount = GetRowCount(); rowNumber < rowCount; rowNumber++) {
				totalElasticity += GetRowElasticity(rowNumber);
			}
			return totalElasticity;
		}
	}

	void GridBox::LayoutRow(Count rowNumber, const POINT &position, const SIZE &size)
	{
		long stretch = size.cx - GetMinimumSize().cx;
		long totalElasticity = GetHorizontalElasticity();
		POINT boxPosition(position);

		if (m_flags & EqualizeColumnWidths) {
			long columnWidth = GetLargestMinimumColumnWidth();
			if ((stretch > 0) && (totalElasticity > 0)) {
				long elasticity = GetSmallestColumnElasticity();
				if (elasticity > 0) {
					columnWidth += (stretch * elasticity) / totalElasticity;
				}
			}
			for (Count columnNumber = 0, columnCount = m_columnCount; columnNumber < columnCount; columnNumber++) {
				Box *box = GetBox(rowNumber, columnNumber);
				if (box != NULL) {
					box->Layout(Construct<SIZE>(columnWidth, size.cy), boxPosition);
				}
				boxPosition.x += columnWidth;
			}
		} else {
			for (Count columnNumber = 0, columnCount = m_columnCount; columnNumber < columnCount; columnNumber++) {
				long columnWidth = GetMinimumColumnWidth(columnNumber);
				if ((stretch > 0) && (totalElasticity > 0)) {
					long elasticity = GetColumnElasticity(columnNumber);
					if (elasticity > 0) {
						columnWidth += (stretch * elasticity) / totalElasticity;
					}
				}
				Box *box = GetBox(rowNumber, columnNumber);
				if (box != NULL) {
					box->Layout(Construct<SIZE>(columnWidth, size.cy), boxPosition);
				}
				boxPosition.x += columnWidth;
			}
		}
	}

	void GridBox::LayoutContent(const POINT &position, const SIZE &size)
	{
		long stretch = size.cy - GetMinimumSize().cy;
		long totalElasticity = GetVerticalElasticity();
		POINT rowPosition(position);

		if (m_flags & EqualizeRowHeights) {
			long rowHeight = GetLargestMinimumRowHeight();
			if ((stretch > 0) && (totalElasticity > 0)) {
				long elasticity = GetSmallestRowElasticity();
				if (elasticity > 0) {
					rowHeight += (stretch * elasticity) / totalElasticity;
				}
			}
			for (Count rowNumber = 0, rowCount = GetRowCount(); rowNumber < rowCount; rowNumber++) {
				LayoutRow(rowNumber, rowPosition, Construct<SIZE>(size.cx, rowHeight));
				rowPosition.y += rowHeight;
			}
		} else {
			for (Count rowNumber = 0, rowCount = GetRowCount(); rowNumber < rowCount; rowNumber++) {
				long rowHeight = GetMinimumRowHeight(rowNumber);
				if ((stretch > 0) && (totalElasticity > 0)) {
					long elasticity = GetRowElasticity(rowNumber);
					if (elasticity > 0) {
						rowHeight += (stretch * elasticity) / totalElasticity;
					}
				}
				LayoutRow(rowNumber, rowPosition, Construct<SIZE>(size.cx, rowHeight));
				rowPosition.y += rowHeight;
			}
		}
	}
}
